﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CompleterLib.AbstractTypes;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using Util;

namespace CompleterLib.Util
{
    static class GraphUtil
    {
        public static IEnumerable<int[]> EnumerateMaximumMatchings(this bool[][] graph)
        {
            int leftNodeCount = graph.Length;
            int rightNodeCount = graph[0].Length;
            List<int> edgeCount = graph.Select(l => l.Count(b => b))
                                       .ToList();
            if (edgeCount.Any(c => c == 0))
            {
                return Enumerable.Empty<int[]>();
            }

            var emptyMatching = Enumerable.Repeat<int>(-1, rightNodeCount)
                                          .ToArray();
            List<int[]> partialMatchings = emptyMatching.Singleton().ToList();
            for (int l = 0; l < leftNodeCount; l++)
            {
                List<int[]> newPartialMatchings = new List<int[]>();
                for (int r = 0; r < rightNodeCount; r++)
                {
                    var partialMatchingForR = partialMatchings.Where(m => m[r] == -1);
                    if (graph[l][r])
                    {
                        foreach (var matching in partialMatchingForR)
                        {
                            var newMatching = (int[])matching.Clone();
                            newMatching[r] = l;
                            newPartialMatchings.Add(newMatching);
                        }
                    }
                }
                partialMatchings = newPartialMatchings;
            }
            return partialMatchings;
        }
    }
    public static class TypeUtil
    {
        public static IEnumerable<ITypeSymbol> SelfAndAncestors(this ITypeSymbol type)
        {
            while (type != null)
            {
                yield return type;
                type = type.BaseType;
            }
        }
        public static IEnumerable<ITypeSymbol> GetOnlySuperTypes(this ITypeSymbol type)
        {
            var res = type.Interfaces.AsEnumerable();
            if (type.BaseType != null)
            {
                res = type.BaseType.Cons(res);
            }
            return res;
        }
        public static IEnumerable<TypeSymbol> GetOnlySuperTypes(this TypeSymbol type)
        {
            var res = type.Interfaces.AsEnumerable();
            if (type.BaseType != null)
            {
                res = type.BaseType.Cons(res);
            }
            return res;
        }
        public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type)
        {
            return type.SelfAndAncestors()
                .SelectMany(t => t.GetMembers())
                .Concat(type.AllInterfaces.AsEnumerable()
                .SelectMany(t => t.GetMembers()));
        }
        public static IEnumerable<MethodSymbol> GetAllInstanceMethods(this TypeSymbol type)
        {
            return type.GetAllMembers()
                .Where(m => m is MethodSymbol && !m.IsStatic)
                .Cast<MethodSymbol>()
                .Where(m => m.CanBeReferencedByName);
        }

        public static bool IsValidType(this TypeSymbol t)
        {
            return !(null == t || t is ErrorTypeSymbol || t.Kind.Equals(SymbolKind.ErrorType));
        }
        public static bool IsValidTypeRecursive(this TypeSymbol t)
        {
            return t.IsValidType()
                && (!(t is NamedTypeSymbol)
                    || ((NamedTypeSymbol)t).TypeArguments
                                           .All(ta => ta.IsValidTypeRecursive()));
        }
        public static bool IsAssignableFrom(this TypeSymbol left, TypeSymbol right,
            SemanticModel semModel)
        {
            if (left.Equals(right))
            {
                // TODO This makes void == void... should anything be "assignable" to void?
                return true;
            }
            var conversion = semModel.Compilation.ClassifyConversion(right, left);
            return conversion.Exists && !conversion.IsExplicit;
        }

        private static bool IsAssignableFromOrUnknownGeneric(this NamedTypeSymbol left,
                NamedTypeSymbol right, SemanticModel semModel)
        {
            // Note: this method is only useful if IsAssignableFrom has already returned false.
            bool leftHasTypeParams = left.Arity > 0;
            if (leftHasTypeParams)
            {
                bool leftHasInvalidTypeParams =
                    left.TypeArguments.SequenceEqual(left.TypeParameters)
                    || left.TypeArguments.Any(ta => !ta.IsValidTypeRecursive());
                if (leftHasInvalidTypeParams)
                {
                    TypeSymbol leftOrig = left.ConstructedFrom;
                    // Do a quick check...
                    if(leftOrig.IsAssignableFrom(right.ConstructedFrom, semModel))
                    {
                        return true;
                    }
                    // ... but if not, check if any of right's ancestors have removable type arguments.
                    // If left is an interface, check right's interfaces, otherwise don't bother.
                    if (left.IsAbstract && left.BaseType == null)
                    {
                        if (right.AllInterfaces.Any(ri =>
                            leftOrig.IsAssignableFrom(ri.ConstructedFrom, semModel)))
                        {
                            return true;
                        }
                    }
                    for (NamedTypeSymbol sym = right.BaseType; sym != null; sym = sym.BaseType)
                    {
                        if (sym.Arity > 0 && leftOrig.IsAssignableFrom(sym.ConstructedFrom, semModel))
                        {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
        private static bool IsAssignableFromOrUnknownGeneric(this TypeSymbol left,
                TypeSymbol right, SemanticModel semModel)
        {
            return left is NamedTypeSymbol
                && right is NamedTypeSymbol
                && ((NamedTypeSymbol)left)
                    .IsAssignableFromOrUnknownGeneric((NamedTypeSymbol)right, semModel);
        }

        public static bool IsAssignableFromOrUnknown(this TypeSymbol left, TypeSymbol right, SemanticModel semModel)
        {
            return !left.IsValidType()
                || !right.IsValidType()
                || left.IsAssignableFrom(right, semModel)
                || left.IsAssignableFromOrUnknownGeneric(right, semModel);
        }

        public static bool IsSameType(this TypeSymbol left, TypeSymbol right,
            SemanticModel semModel)
        {
            if (left.Equals(right))
            {
                // TODO This makes void == void... should anything be "assignable" to void?
                return true;
            }
            else //if (left.IsAssignableFrom(right, semModel))
            {
                // TODO Is this right?
                /*var conversion = semModel.Compilation.ClassifyConversion(
                    right.OriginalDefinition as TypeSymbol, left.OriginalDefinition as TypeSymbol);*/
                var conversion = semModel.Compilation.ClassifyConversion(right, left);
                return conversion.IsIdentity;
            }
            /*
            else
            {
                return false;
            }*/
        }
        public static int DistanceFrom(this TypeSymbol left, TypeSymbol right,
            SemanticModel semModel)
        {
            var conversion = semModel.Compilation.ClassifyConversion(right, left);
            if (!conversion.Exists || conversion.IsExplicit)
            {
                if (left.Kind.Equals(SymbolKind.ErrorType) && left.Name.Length == 1)
                {
                    // left is probably a generic type argument
                    return semModel.Compilation.ObjectType.DistanceFrom(right, semModel);
                }
                else if (left.IsAssignableFromOrUnknownGeneric(right, semModel))
                {
                    TypeSymbol leftOrig = ((NamedTypeSymbol)left).ConstructedFrom;
                    TypeSymbol rightOrig = ((NamedTypeSymbol)right).ConstructedFrom;
                    if (leftOrig.IsAssignableFrom(rightOrig, semModel))
                    {
                        return leftOrig
                            .DistanceFrom(rightOrig, semModel);
                    }
                    else if (right.BaseType.IsValidType()
                        && (left.IsAssignableFrom(right.BaseType, semModel)
                            || left.IsAssignableFromOrUnknownGeneric(right.BaseType, semModel)))
                    {
                        return 1 + right.BaseType
                                .Cons(right.Interfaces
                                           .Where(ri => leftOrig.IsAssignableFrom(ri.ConstructedFrom, semModel)))
                                .Select(ri => leftOrig.DistanceFrom(ri.ConstructedFrom, semModel))
                            .Min();
                    }
                    else
                    {
                        return 1 + right.Interfaces
                            .Where(ri => leftOrig.IsAssignableFrom(ri.ConstructedFrom, semModel))
                            .Select(ri => leftOrig.DistanceFrom(ri.ConstructedFrom, semModel))
                            .Min();
                    }
                }
                else
                {
                    throw new Exception("No implicit conversion from "
                        + right + " to " + left + ".");
                }
            }
            else if (conversion.IsIdentity)
            {
                return 0;
            }
            else if (conversion.IsNullLiteral)
            {
                return 0; // TODO ?
            }
            else if (conversion.IsEnumeration)
            {
                return 1;
            }
            else if (conversion.IsNumeric)
            {
                return 1;
            }
            else if (conversion.IsReference)
            {
                var rightSuper = right.BaseType;
                if (!rightSuper.IsValidType() || !left.IsAssignableFrom(rightSuper, semModel))
                {
                    var ifaceDists = right.Interfaces
                        .Select(i => left.IsAssignableFrom(i, semModel)
                                     ? left.DistanceFrom(i, semModel)
                                     : (left is NamedTypeSymbol
                                        && ((NamedTypeSymbol)left).ConstructedFrom
                                            .IsAssignableFromOrUnknownGeneric(i, semModel))
                                     ? ((NamedTypeSymbol)left).ConstructedFrom.DistanceFrom(i, semModel)
                                     : (int?)null)
                        .Where(i => i != null)
                        .Select(i => i.Value)
                        .ToList();
                    if (ifaceDists.Any())
                    {
                        return 1 + ifaceDists.Min();
                    }
                    else if (left is ArrayTypeSymbol && right is ArrayTypeSymbol)
                    {
                        var leftArr = left as ArrayTypeSymbol;
                        var rightArr = right as ArrayTypeSymbol;
                        if (leftArr.Rank == rightArr.Rank)
                        {
                            return leftArr.BaseType.DistanceFrom(rightArr.BaseType, semModel);
                        }
                        else if (leftArr.Rank < rightArr.Rank)
                        {
                            return leftArr.BaseType.DistanceFrom(
                                semModel.Compilation.CreateArrayTypeSymbol(
                                    rightArr.ElementType, rightArr.Rank - leftArr.Rank),
                                semModel);
                        }
                        else // leftArr.Rank > rightArr.Rank
                        {
                            return semModel.Compilation.CreateArrayTypeSymbol(
                                    leftArr.ElementType, leftArr.Rank - rightArr.Rank)
                                .DistanceFrom(rightArr.BaseType, semModel);
                        }
                    }
                    else if (left.OriginalDefinition.Equals(right.OriginalDefinition))
                    {
                        return 1; // TODO Generic type distance?
                    }
                    else if (left.Equals(semModel.Compilation.ObjectType)
                        && right.BaseType == null)
                    {
                        // This should mean that right is an interface.
                        return 1;
                    }
                    else if (rightSuper.TypeKind.Equals(TypeKind.Error))
                    {
                        // Huh? This seems to happen when a reference fails to load...
                        return 2; // Must be at least 2 as rightSuper is not in context but left and right are.
                    }
                    else
                    {
                        throw new Exception("Unsupported implicit reference conversion from "
                            + right + " to " + left + ": " + conversion);
                    }
                }
                else
                {
                    return 1 + left.DistanceFrom(rightSuper, semModel);
                }
            }
            else if (conversion.IsBoxing)
            {
                return 1; // TODO Right value? For boxing enums to object...
            }
            else
            {
                throw new Exception("Unsupported implicit conversion from "
                    + right + " to " + left + ": " + conversion);
            }
        }

        public static bool CoalesceType(this TypeSymbol A, TypeSymbol B, out TypeSymbol res,
            SemanticModel semModel)
        {
            if(!(A.IsValidType() && B.IsValidType())) {
                res = null;
                return true;
            }
            // Reference: http://en.csharp-online.net/ECMA-334%3A_14.12_The_null_coalescing_operator
            TypeSymbol A0 = null;
            if (A is NamedTypeSymbol && (A as NamedTypeSymbol).ConstructedFrom
                .SpecialType.Equals(SpecialType.System_Nullable_T))
            {
                A0 = (A as NamedTypeSymbol).TypeArguments.First();
                var bToA0 = semModel.Compilation.ClassifyConversion(B, A0);
                if (bToA0.IsImplicit)
                {
                    res = A0;
                    return true;
                }
            }
            if (semModel.Compilation.ClassifyConversion(B, A).IsImplicit)
            {
                res = A;
                return true;
            }
            else if (A0 != null && semModel.Compilation.ClassifyConversion(A0, B).IsImplicit)
            {
                res = B;
                return true;
            }
            res = null;
            return false;
        }
    }
    static class GlobalsUtil
    {
        public static IEnumerable<Symbol> GetGlobals(this SemanticModel semModel)
        {
            return semModel.Compilation.GlobalNamespace.GetGlobals();
        }
        private static IEnumerable<Symbol> GetGlobals(this NamespaceSymbol ns)
        {
            foreach (Symbol sym in ns.GetMembers())
            {
                if (sym is NamespaceSymbol)
                {
                    foreach (Symbol global in (sym as NamespaceSymbol).GetGlobals())
                    {
                        yield return global;
                    }
                }
                else if (sym is NamedTypeSymbol)
                {
                    foreach (Symbol global in (sym as NamedTypeSymbol).GetGlobals())
                    {
                        yield return global;
                    }
                }
            }
        }
        private static IEnumerable<Symbol> GetGlobals(this NamedTypeSymbol type)
        {
            if (type.CanBeReferencedByName)
            {
                foreach (NamedTypeSymbol nestedType in type.GetTypeMembers())
                {
                    foreach (Symbol global in nestedType.GetGlobals())
                    {
                        yield return global;
                    }
                }
                foreach (Symbol global in type.GetAllMembers()
                                              .Where(sym => sym.IsStatic
                                                  && (sym as Symbol).CanBeReferencedByName
                                                  && (sym is FieldSymbol
                                                    || sym is PropertySymbol
                                                    || (sym is MethodSymbol
                                                        && (sym as MethodSymbol).Parameters.Count == 0))))
                {
                    yield return global;
                }
            }
        }
    }
    static class MethodUtil
    {
        public static IEnumerable<MethodSymbol> GetAllStaticMethodsForArgs(this SemanticModel semModel,
            IEnumerable<TypeSymbol> types)
        {
            if (types.Any())
            {
                return MethodCacheUtil.GetSmallestMethodCacheForTypes(types, semModel)
                    .AllStaticMethods
                    .Concat(semModel.Compilation.Assembly.GlobalNamespace.GetAllStaticMethods());
            }
            else
            {
                return semModel.GetAllStaticMethods();
            }
        }
        public static IEnumerable<MethodSymbol> GetAllStaticMethods(this SemanticModel semModel)
        {
            return semModel.Compilation.GlobalNamespace.GetAllStaticMethods();
        }
        private static IEnumerable<MethodSymbol> GetAllStaticMethods(this NamespaceSymbol ns)
        {
            foreach (Symbol sym in ns.GetMembers())
            {
                if (sym is NamespaceSymbol)
                {
                    foreach (MethodSymbol meth in (sym as NamespaceSymbol).GetAllStaticMethods())
                    {
                        yield return meth;
                    }
                }
                else if (sym is NamedTypeSymbol)
                {
                    foreach (MethodSymbol meth in (sym as NamedTypeSymbol).GetAllStaticMethods())
                    {
                        yield return meth;
                    }
                }
            }
        }
        public static IEnumerable<IMethodSymbol> GetInstanceConstructors(this NamedTypeSymbol type)
        {
            return type.GetConstructors(); // ??? Is this right?
        }
        private static IEnumerable<MethodSymbol> GetAllStaticMethods(this NamedTypeSymbol type)
        {
            if (type.CanBeReferencedByName)
            {
                foreach (NamedTypeSymbol nestedType in type.GetTypeMembers())
                {
                    foreach (MethodSymbol meth in nestedType.GetAllStaticMethods())
                    {
                        yield return meth;
                    }
                }
                foreach (MethodSymbol c in type.GetInstanceConstructors())
                {
                    yield return c;
                }
                foreach (MethodSymbol meth in type.GetAllMembers()
                    .Where(m => m is MethodSymbol && m.IsStatic)
                    .Cast<MethodSymbol>()
                    .Where(m => m.CanBeReferencedByName))
                {
                    yield return meth;
                }
            }
        }
    }
    static class MethodNameUtil
    {
        public static string GetQualifiedName(this MethodSymbol meth) {
            return meth.ContainingType.ToString()
                + "::" + meth.Name
                + "<" + meth.TypeArguments.AsEnumerable().CommaSeparated() + ">"
                + "(" + meth.Parameters.Select(p => p.Type).CommaSeparated() + ")";
        }
        public static bool HasSameMethodName(this MethodSymbol left, MethodSymbol right)
        {
            // TODO Use ContainingType.OriginalDefinition instead? I don't think so...
            return left.Name.Equals(right.Name)
                && left.ContainingType.Equals(right.ContainingType);
        }
        public static MethodSymbol GetBaseOfOverride(this MethodSymbol meth)
        {
            MethodSymbol next = meth;
            do
            {
                meth = next;
                next = meth.OverriddenMethod;
            }
            while(next != null);
            return meth;
        }
    }

    public static class BestMatchUtil
    {
        public static CommonSyntaxNode GetBestMatch(CommonSyntaxNode node, TextSpan span, string kind = null)
        {
            if (node.FullSpan.Contains(span))
            {
                CommonSyntaxNode possibleMatch = null;
                foreach (CommonSyntaxNode child in node.ChildNodes())
                {
                    CommonSyntaxNode match = GetBestMatch(child, span, kind);
                    if ((match ?? child) != child)
                    {
                        return match;
                    }
                    else if (match != null)
                    {
                        possibleMatch = match;
                    }
                }
                return possibleMatch ?? node;
            }
            else if (span.Contains(node.Span))
            {
                return null;
            }
            else
            {
                return null;
            }
        }

        public static CommonSyntaxNode GetClosestStatement(CommonSyntaxNode node, TextSpan span)
        {
            if (node is StatementSyntax && !(node is BlockSyntax))
            {
                return node;
            }
            else if (node is FieldDeclarationSyntax)
            {
                return node;
            }
            else if (node.FullSpan.Contains(span))
            {
                foreach (CommonSyntaxNode child in node.ChildNodes())
                {
                    CommonSyntaxNode match = GetClosestStatement(child, span);
                    if (match != null)
                    {
                        return match;
                    }
                }
                return null;
            }
            else if (span.Contains(node.Span))
            {
                return null;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Expands a match to include entire relevant expression.
        /// </summary>
        /// <param name="match"></param>
        /// <param name="selection">True if any text was selected by the user
        ///     (normal usage is to just expand from a single cursor position).</param>
        /// <param name="semModel"></param>
        /// <returns></returns>
        public static CommonSyntaxNode ExpandMatch(
                this CommonSyntaxNode match, bool selection, SemanticModel semModel)
        {
            if (match == null)
            {
                return null;
            }
            while (!(match is PredefinedTypeSyntax)
                && (match.Parent is MemberAccessExpressionSyntax
                || match.Parent is InvocationExpressionSyntax
                || match.Parent is ObjectCreationExpressionSyntax
                || !selection && match.Parent is ArgumentSyntax
                || match is ArgumentListSyntax
                || match is ArgumentSyntax
                || (match.Parent is BinaryExpressionSyntax &&
                    (match.Parent as BinaryExpressionSyntax).Kind
                        .Equals(SyntaxKind.AsExpression))
                || (match.Parent is BinaryExpressionSyntax
                    && (match.Parent as BinaryExpressionSyntax)
                        .Left.IsPartial(semModel)
                    && (match.Parent as BinaryExpressionSyntax)
                        .Right.IsPartial(semModel))
                || (match.Parent is BinaryExpressionSyntax
                    && !match.IsPartial(semModel)
                    && match.Parent.IsPartial(semModel))))
            {
                match = match.Parent;
            }
            return match;
        }

        public static int? FindFieldDeclPosition(CommonSyntaxNode node)
        {
            if (node is TypeDeclarationSyntax)
            {
                return ((TypeDeclarationSyntax)node).OpenBraceToken.FullSpan.End;
            }
            foreach (CommonSyntaxNode child in node.ChildNodes())
            {
                int? match = FindFieldDeclPosition(child);
                if (match != null)
                {
                    return match;
                }
            }
            return null;
        }
    }

    public static class HashUtil
    {
        public static string ToHexString(this byte[] xs)
        {
            return string.Join("", xs.Select(x => x.ToString("x2")).ToArray());
        }
    }

    public static class BaseDirectoryUtil
    {
        private static string extensionDirectory = null;
        public static string ExtensionDirectory
        {
            get { return extensionDirectory; }
            set
            {
                extensionDirectory = value;
                TypeTermParser.ExtensionDirectory = value;
            }
        }
    }

    public static class ReferenceUtil
    {
        public static IList<MetadataReference> GetAllReferences(this Compilation comp)
        {
            HashSet<MetadataReference> res = new HashSet<MetadataReference>();
            HashSet<Compilation> visited = new HashSet<Compilation>();
            Stack<Compilation> toVisit = new Stack<Compilation>();
            toVisit.Push(comp);

            while (toVisit.Count > 0)
            {
                var current = toVisit.Pop();
                if (!visited.Contains(current))
                {
                    visited.Add(current);
                    foreach (var r in current.References)
                    {
                        res.Add(r);
                        if (r is CompilationReference)
                        {
                            toVisit.Push((r as CompilationReference).Compilation);
                        }
                    }
                }
            }

            return res.ToList();
        }
    }

    public static class SymbolUtil
    {
        public static Symbol ResolveAlias(this Symbol sym)
        {
            while (sym is AliasSymbol)
            {
                sym = (sym as AliasSymbol).Target;
            }
            return sym;
        }
    }

    public static class ContainingTypeUtil
    {
        public static TypeSymbol GetContainingTypeAt(this SemanticModel semModel, int position)
        {
            return (BestMatchUtil.GetBestMatch(semModel.SyntaxTree.GetRoot(), new TextSpan(position, 0)) as SyntaxNode)
                    .AncestorsAndSelf()
                    .Where(e => e is BaseTypeDeclarationSyntax)
                    .Cast<BaseTypeDeclarationSyntax>()
                    .Select(e => semModel.GetDeclaredSymbol(e))
                    .FirstOrDefault();
        }
    }

    public static class SemanticInfoUtil
    {
        public static SemanticInfo GetSemanticInfo(this SemanticModel semModel, ExpressionSyntax exp)
        {
            return new SemanticInfo(semModel, exp);
        }
    }
    /// <summary>
    /// Wrapper to ease transition from Roslyn July 2011 to Roslyn June 2012.
    /// Should remove all uses of this class and replace with new way of doing things.
    /// </summary>
    public class SemanticInfo
    {
        private readonly SemanticModel semModel;
        private readonly ExpressionSyntax exp;

        public SemanticInfo(SemanticModel semModel, ExpressionSyntax exp)
        {
            this.semModel = semModel;
            this.exp = exp;
        }

        public Symbol Symbol { get { return semModel.GetSymbolInfo(exp).Symbol; } }
        public TypeSymbol Type { get { return semModel.GetTypeInfo(exp).Type; } }
    }
    public static class RoslynJuly2011Helpers
    {
        public static MethodSymbol GetDeclaredMethodSymbol(this SemanticModel semModel, SyntaxNode e)
        {
            if(e is BaseMethodDeclarationSyntax) {
                return semModel.GetDeclaredSymbol(e as BaseMethodDeclarationSyntax);
            }
            else if (e is AccessorDeclarationSyntax)
            {
                return semModel.GetDeclaredSymbol(e as AccessorDeclarationSyntax);
            }
            else
            {
                throw new Exception("Invalid argument to GetDeclaredMethodSymbol(): e's type is " + e.GetType());
            }
        }
    }
}
